package errutl

import (
	"bytes"
	"errors"
	"fmt"
	"path/filepath"
	"runtime"
	"strconv"
	"sync"
)

type wrapError struct {
	msg string
	err error
}

func (e *wrapError) Error() string {
	return e.msg
}

func (e *wrapError) Unwrap() error {
	return e.err
}

// Wrap 平时返回错误使用这个方法进行包装
func Wrap(err error) error {
	return WrapSkip(err, 1)
}

func WrapSkip(err error, skip int) error {
	if err == nil {
		return nil
	}

	pc, file, line, _ := runtime.Caller(1 + skip)
	fn := runtime.FuncForPC(pc)

	msg := err.Error() +
		"\nPass by " + filepath.Base(fn.Name()) +
		"\n\t" + file + ":" + strconv.Itoa(line) + " "

	return &wrapError{
		msg: msg,
		err: err,
	}
}

// WrapWithMsg 平时返回错误使用这个方法进行包装
func WrapWithMsg(err error, format string, args ...interface{}) error {
	return WrapSkipWithMsg(err, 1, format, args...)
}

func WrapSkipWithMsg(err error, skip int, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}

	var msg string
	if len(args) == 0 {
		msg = format
	} else {
		msg = fmt.Sprintf(format, args...)
	}

	pc, file, line, _ := runtime.Caller(1 + skip)
	fn := runtime.FuncForPC(pc)

	msg = err.Error() +
		"\nPass by " + filepath.Base(fn.Name()) + " (" + msg +
		")\n\t" + file + ":" + strconv.Itoa(line) + " "

	return &wrapError{
		msg: msg,
		err: err,
	}
}

func New(format string, args ...interface{}) error {
	if len(args) == 0 {
		return WrapSkip(errors.New(format), 1)
	}

	return WrapSkip(fmt.Errorf(format, args...), 1)
}

// Cause 返回最原始的错误，可用来断言
func Cause(err error) error {
	if err == nil {
		return nil
	}

	for {
		e := errors.Unwrap(err)
		if e == nil {
			break
		}
		err = e
	}

	return err
}

func NewErrs(err ...error) *Errors {
	errs := &Errors{
		lock: sync.Mutex{},
	}
	errs.Append(err...)

	return errs
}

type Errors struct {
	massages []string
	lock     sync.Mutex
}

func (e *Errors) Error() string {

	e.lock.Lock()
	defer e.lock.Unlock()

	if len(e.massages) == 0 {
		return ""
	}

	bf := bytes.Buffer{}
	for _, msg := range e.massages {
		bf.WriteString("<" + msg + ">")
	}

	return bf.String()
}

func (e *Errors) Append(err ...error) {

	e.lock.Lock()
	defer e.lock.Unlock()

	for _, er := range err {
		if er == nil {
			e.massages = append(e.massages, "nil")
		} else {
			e.massages = append(e.massages, er.Error())
		}
	}
}

func (e *Errors) Write(msg string, args ...interface{}) {

	e.lock.Lock()
	defer e.lock.Unlock()

	if len(msg) == 0 {
		e.massages = append(e.massages, "nil")
		return
	}

	if len(args) == 0 {
		e.massages = append(e.massages, msg)
	} else {
		e.massages = append(e.massages, fmt.Sprintf(msg, args...))
	}
}

func (e *Errors) IsNil() bool {
	e.lock.Lock()
	defer e.lock.Unlock()

	return len(e.massages) == 0
}

func (e *Errors) IsNotNil() bool {
	e.lock.Lock()
	defer e.lock.Unlock()

	return len(e.massages) != 0
}

func CheckErr(err error) {
	if err != nil {
		panic(err.Error())
	}
}
